package com.tarena.lbs.business.dao.repository.impl;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.tarena.lbs.business.dao.mapper.BusinessMapper;
import com.tarena.lbs.business.dao.mapper.StoreMapper;
import com.tarena.lbs.business.dao.repository.StoreRepository;
import com.tarena.lbs.business.pojo.param.UserLocationParam;
import com.tarena.lbs.business.pojo.po.BusinessPO;
import com.tarena.lbs.business.pojo.po.StorePO;
import com.tarena.lbs.business.pojo.query.AreaStoreQuery;
import com.tarena.lbs.business.pojo.query.StoreQuery;
import com.tarena.lbs.business.pojo.vo.NearStoreVO;
import com.tarena.lbs.common.constant.CacheKeyConst;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Repository
public class StoreRepositoryImpl implements StoreRepository {

    @Autowired
    private StoreMapper storeMapper;

    @Autowired
    private BusinessMapper businessMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public BusinessPO getBusinessById(Integer businessId) {
        return businessMapper.selectById(businessId);
    }

    @Override
    public void insertStore(StorePO storePO) {
        storeMapper.insert(storePO);
    }

    @Override
    public PageInfo<StorePO> pagesList(StoreQuery query) {
        QueryWrapper<StorePO> queryWrapper = assembleStoreQuery(query);
        //PageHelper.startPage 之间不能有代码
        PageHelper.startPage(query.getPageNo(), query.getPageSize());
        List<StorePO> pos = storeMapper.selectList(queryWrapper);
        log.info("store 分页查询结果：{}", pos);
        //selectList 数据库操作 之间不能有代码
        return new PageInfo<>(pos);
    }

    @Override
    public PageInfo<StorePO> getAreaStorePageList(Integer businessId, AreaStoreQuery query) {
        QueryWrapper<StorePO> queryWrapper = assembleAreaStoreQuery(businessId, query);
        //PageHelper.startPage 之间不能有代码
        PageHelper.startPage(query.getPageNo(), query.getPageSize());
        List<StorePO> pos = storeMapper.selectList(queryWrapper);
        log.debug("AreaStoreQuery 分页查询结果：{}", pos);
        //selectList 数据库操作 之间不能有代码
        return new PageInfo<>(pos);
    }

    @Override
    public StorePO getStoreById(Integer storeId) {
        return storeMapper.selectById(storeId);
    }

    @Override
    public void deleteStore(Integer storeId, Integer businessId) {
        QueryWrapper<StorePO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", storeId);
        queryWrapper.eq("business_id", businessId);
        storeMapper.delete(queryWrapper);
    }

    @Override
    public List<NearStoreVO> getNearStores(UserLocationParam param) {
        GeoOperations<String, Integer> geoOps = redisTemplate.opsForGeo();
        Circle circle = new Circle(
                new Point(Double.parseDouble(param.getLongitude()), Double.parseDouble(param.getLatitude())),
                new Distance(5.00, RedisGeoCommands.DistanceUnit.KILOMETERS));
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                        .includeDistance()
                        .includeCoordinates();
        GeoResults<RedisGeoCommands.GeoLocation<Integer>> geoResults = geoOps.radius(CacheKeyConst.GEO_STORE_LOCATION_KEY, circle, args);
        List<GeoResult<RedisGeoCommands.GeoLocation<Integer>>> results = geoResults.getContent();
        List<NearStoreVO> stores = null;
        if (results != null && results.size() > 0) {
            stores = results.stream().map(result -> {
                NearStoreVO store = new NearStoreVO();
                Distance distance = result.getDistance();
                BigDecimal distValue = new BigDecimal(distance.getValue() + "").setScale(2, RoundingMode.HALF_UP);
                Integer storeId = result.getContent().getName();
                Double Lng = result.getContent().getPoint().getX();
                Double Lat = result.getContent().getPoint().getY();
                store.setDistance(distValue);
                store.setUserId(param.getUserId());
                store.setStoreId(storeId);
                store.setLatitude(Lng);
                store.setLongitude(Lat);
                return store;
            }).collect(Collectors.toList());
        }
        return stores;
    }

    @Override
    public Boolean geoStationsExists() {
        return redisTemplate.hasKey(CacheKeyConst.GEO_STORE_LOCATION_KEY);
    }

    @Override
    public Long countAll() {
        return storeMapper.selectCount(null);
    }

    @Override
    public List<StorePO> getStationsPage(int pageNum, int pageSize) {
        QueryWrapper<StorePO> queryWrapper = new QueryWrapper<>();
        PageHelper.startPage(pageNum, pageSize);
        return storeMapper.selectList(queryWrapper);
    }

    @Override
    public void saveGeos(List<StorePO> pos) {
        //geoadd charging:stations:all lng1 lat1 1 lng2 lat2 2 lng3 lat3 3
        List<RedisGeoCommands.GeoLocation<Integer>> geoLocations = null;
        geoLocations = pos.stream().map(po -> {
            RedisGeoCommands.GeoLocation<Integer> geoLocation =
                    new RedisGeoCommands.GeoLocation<>(po.getId(),
                            new Point(Double.parseDouble(po.getStoreLongitude()), Double.parseDouble(po.getStoreLatitude())));
            return geoLocation;
        }).collect(Collectors.toList());
        redisTemplate.opsForGeo().add(CacheKeyConst.GEO_STORE_LOCATION_KEY, geoLocations);
    }

    @Override
    public void disableStore(Integer storeId, Integer businessId) {
        UpdateWrapper<StorePO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.set("store_status", -1);
        updateWrapper.set("update_time", new Date());
        updateWrapper.eq("id", storeId);
        updateWrapper.eq("business_id", businessId);
        storeMapper.update(updateWrapper);
    }

    @Override
    public void enableStore(Integer storeId, Integer businessId) {
        UpdateWrapper<StorePO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.set("store_status", 0);
        updateWrapper.set("update_time", new Date());
        updateWrapper.eq("id", storeId);
        updateWrapper.eq("business_id", businessId);
        storeMapper.update(updateWrapper);
    }

    @Override
    public void updateStore(StorePO storePO) {
        storeMapper.updateById(storePO);
    }

    private QueryWrapper<StorePO> assembleAreaStoreQuery(Integer businessId, AreaStoreQuery query) {
        QueryWrapper<StorePO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("business_id", businessId);
        for (Long cityId : query.getCityIdList()) {
            queryWrapper.or().eq("city_id", cityId);
        }
        return queryWrapper;
    }

    private QueryWrapper<StorePO> assembleStoreQuery(StoreQuery query) {
        QueryWrapper<StorePO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("business_id", query.getBusinessId());
        if (StringUtils.isNotBlank(query.getStoreName())) {
            queryWrapper.like("store_name", query.getStoreName());
        }
        if (query.getAreaId() != null) {
            queryWrapper.eq("area_id", query.getAreaId());
        }
        if (query.getCityId() != null) {
            queryWrapper.eq("city_id", query.getCityId());
        }
        if (query.getProvinceId() != null) {
            queryWrapper.eq("province_id", query.getProvinceId());
        }
        if (StringUtils.isNotBlank(query.getStoreHeadPhone())) {
            queryWrapper.eq("store_head_phone", query.getStoreHeadPhone());
        }
        if (query.getStartingTime() != null) {
            queryWrapper.ge("update_time", query.getStartingTime());
        }
        if (query.getEndTime() != null) {
            queryWrapper.le("update_time", query.getEndTime());
        }
        if (query.getStoreStatus() != null) {
            queryWrapper.eq("store_status", query.getStoreStatus());
        }
        return queryWrapper;
    }

}
